# hex.rb
# usage: ruby hex.rb yourUsername yourPassword opponentUsername
# Programming by Eric Rollins
#
# Copyright (c) 2002 Eric Rollins
# www.acm.org/~rollins
# hex comes with Absolutely No Warranty.
# This is free software, and you are welcome to
# redistribute it under certain conditions
# (for details see:GNU General Public License,
# http://www.gnu.org/copyleft/gpl.html)
# version 1.3 - 11x11 board
require 'tk'
# uses ruby jabber library
require 'rjab/connection'
# constants
SIDE_NONE = 0
SIDE_RED = 1
SIDE_BLUE = 2
# edit here for board size
ROWS = 11
COLS = 11
#============================================================
def oppositeSide(side)
if side == SIDE_RED
return SIDE_BLUE
elsif side == SIDE_BLUE
return SIDE_RED
end
return SIDE_NONE
end
#============================================================
# View
class Canvas
SCALE = 20
XOFF = 20
YOFF = 20
WIDTH = (2.8*SCALE*COLS).to_i
HEIGHT = (1.7*SCALE*ROWS).to_i
def initialize(parent)
@canvas = TkCanvas.new(parent) { width WIDTH; height HEIGHT }
@canvas.pack
@canvas.bind("ButtonRelease-1", proc{ |x, y| do_release(x,y)}, "%x %y")
@pieces = Array.new
drawPiece(3,-2,SIDE_BLUE,FALSE)
drawPiece(3,(COLS+1),SIDE_BLUE,FALSE)
end
#------------------------------------------------------------------------------------------------------------------------
def clear
for p in @pieces
@canvas.delete(p)
end
end
#------------------------------------------------------------------------------------------------------------------------
def drawHex(row,col)
x = XOFF + row*0.86*SCALE+col*1.72*SCALE
y = YOFF + row*1.5*SCALE
TkcLine.new(@canvas,
x, 0.5*SCALE + y,
0.86*SCALE + x, y,
1.72*SCALE + x, 0.5*SCALE + y,
1.72*SCALE + x, 1.5*SCALE + y,
0.86*SCALE + x, 2.0*SCALE + y,
x, 1.5*SCALE + y,
x, 0.5*SCALE + y,
0.86*SCALE + x, y)
end
#------------------------------------------------------------------------------------------------------------------------
def drawPiece(row,col,side,inPieces = TRUE)
x = XOFF + row*0.86*SCALE+col*1.72*SCALE
y = YOFF + row*1.5*SCALE
if side != SIDE_NONE
circle = TkcOval.new(@canvas,
x+ SCALE/3.5,
y + SCALE/3.5,
x + 2.0*SCALE - SCALE/2.3,
y + 2.0*SCALE - SCALE/2.3)
if inPieces
@pieces << circle
end
if side == SIDE_RED
circle.fill('red')
elsif side == SIDE_BLUE
circle.fill('blue')
end
end
end
#------------------------------------------------------------------------------------------------------------------------
def do_release(x,y)
if $app.getSide == SIDE_NONE || $app.getWaiting
return
end
row = (y - YOFF) / (1.5*SCALE)
if row < 0 || row > (ROWS)
return
end
row = row.to_i
col = (x - XOFF - row*0.86*SCALE) / (1.72*SCALE)
if col < 0 || col > (COLS)
return
end
col = col.to_i
if $app.getGame.getBoard.getCell(row,col) != SIDE_NONE
return
end
$app.getGame.setCell(row,col,$app.getSide)
$app.send_turn(row,col)
end
end
#============================================================
# Model
class Board
def initialize
@board = Array.new
for row in 0..ROWS-1
@board[row] = Array.new
end
clear
end
#-----------------------------------------------------------------------------------------------------------------------
def clear
for row in 0..ROWS-1
for col in 0..COLS-1
@board[row][col] = SIDE_NONE
end
end
end
#-----------------------------------------------------------------------------------------------------------------------
def getCell(row,col)
return @board[row][col]
end
#------------------------------------------------------------------------------------------------------------------------
def setCell(row,col,val)
@board[row][col] = val
end
end
#============================================================
# controller for Canvas (View) + Board (Model)
class Game
def initialize(parent)
@canvas = Canvas.new(parent)
@board = Board.new
end
#------------------------------------------------------------------------------------------------------------------------
def drawAll
for row in 0..(ROWS-1)
for col in 0..(COLS-1)
@canvas.drawHex(row,col)
@canvas.drawPiece(row,col,@board.getCell(row,col))
end
end
end
#------------------------------------------------------------------------------------------------------------------------
def setCell(row,col,side)
cur = @board.getCell(row,col)
if cur == SIDE_RED || cur == SIDE_BLUE
puts "ERROR: cell already set!!"
exit
end
@board.setCell(row,col,side)
@canvas.drawPiece(row,col,side)
end
#------------------------------------------------------------------------------------------------------------------------
def newGame
@canvas.clear
@board.clear
end
#------------------------------------------------------------------------------------------------------------------------
def getBoard
return @board
end
end
#============================================================
class Connection
def initialize(username,password,opponent)
@username = username
@password = password
@opponent = "%s@jabber.org" % opponent
@conn = Jabber::Connection.new('jabber.org',nil,nil,'local')
end
#------------------------------------------------------------------------------------------------------------------------
def connect
@conn.connect
@conn.register_handler('message') do | node |
puts node
if node.root.elements['error'] != nil
puts 'ERROR: network error'
exit
else
@readData = node.root.elements['body'].text
end
end
@conn.auth(@username,@password,'hex')
@conn.send('')
end
#------------------------------------------------------------------------------------------------------------------------
def disconnect
@conn.disconnect
end
#------------------------------------------------------------------------------------------------------------------------
def send(message)
m = Jabber::Node.new_message(@opponent, message)
@conn.send(m)
end
#------------------------------------------------------------------------------------------------------------------------
def receive
@readData = nil
while @readData == nil
@conn.process(1)
end
return @readData
end
end
#============================================================
class App
WIDTH = 400
def recv_turn
setWaiting(TRUE)
Thread.new {
puts "about to blocking recv"
data = @conn.receive
puts "recvd: " + data
setStatusText('opponent move ' + data + ' ; select move')
splitData = data.split(',')
row = splitData[0].to_i
col = splitData[1].to_i
if (row < 0) || (row > (ROWS-1)) || (col < 0) || (col > (COLS-1))
puts "ERROR: recd row or col out of bounds"
exit
end
@game.setCell(row,col,oppositeSide($app.getSide))
setWaiting(FALSE)
}
end
#------------------------------------------------------------------------------------------------------------------------
def send_turn(row,col)
msg = "%d,%d" % [row,col]
puts "about to send: " + msg
@conn.send(msg)
setStatusText('Waiting for opponents move...')
recv_turn
end
#------------------------------------------------------------------------------------------------------------------------
def start_blue
if $app.getWaiting
return
end
setSide(SIDE_BLUE)
@game.newGame
@rNewBtn.state('disabled')
@bNewBtn.state('disabled')
setSideMsg('Side = Blue')
setStatusText('Waiting for Red opponents first move...')
recv_turn
end
#------------------------------------------------------------------------------------------------------------------------
def start_red
if $app.getWaiting
return
end
setSide(SIDE_RED)
@game.newGame
@rNewBtn.state('disabled')
@bNewBtn.state('disabled')
setSideMsg('Side = Red')
setStatusText('Select move')
end
#------------------------------------------------------------------------------------------------------------------------
def setStatusText(msg)
@status.text(msg)
@status.width(WIDTH)
@status.pack
end
#------------------------------------------------------------------------------------------------------------------------
def getSide
return @side
end
def setSide(side)
@side = side
end
#------------------------------------------------------------------------------------------------------------------------
def setSideMsg(msg)
@sideMsg.text(msg)
@sideMsg.width(WIDTH)
@sideMsg.pack
end
#------------------------------------------------------------------------------------------------------------------------
def getWaiting
return @waiting
end
def setWaiting(waiting)
@waiting = waiting
end
#------------------------------------------------------------------------------------------------------------------------
def getGame
return @game
end
#------------------------------------------------------------------------------------------------------------------------
def initialize
@side = SIDE_NONE
@waiting = FALSE
end
#------------------------------------------------------------------------------------------------------------------------
def run
ph = { 'padx' => 10, 'pady' => 10 } # tk formatting
if ARGV[0] != nil
titleMsg = "Hex - " + ARGV[0]
else
titleMsg = "Hex - "
end
root = TkRoot.new { title titleMsg }
top = TkFrame.new(root)
@game = Game.new(top)
@game.drawAll
@sideMsg = TkMessage.new(top) { text 'Side = Unset' ; width WIDTH; pack ph }
@rNewBtn = TkButton.new(top) { text 'Start New Game As Red (moves first)'; pack ph; command { $app.start_red}}
@bNewBtn = TkButton.new(top) { text 'Start New Game As Blue (moves second)'; pack ph; command { $app.start_blue}}
@status = TkMessage.new(top) { text 'Press '; width WIDTH; pack ph }
top.pack
if ARGV.length != 3
setStatusText('ERROR: must specify on command line')
@rNewBtn.state('disabled')
@bNewBtn.state('disabled')
else
@conn = Connection.new(ARGV[0],ARGV[1],ARGV[2])
@conn.connect
end
begin
Tk.mainloop
ensure
if @conn != nil
@conn.disconnect
puts 'disconnected'
end
end
end
end
#============================================================
$app = App.new
$app.run